#pragma warning(disable : 4355)
#include "Avalon/WebServices/HttpServer.hpp"
#include "Avalon/Base/Algorithm.hpp"
#include "Avalon/IO/Channel.hpp"
#include "Avalon/IO/ChannelAdapterServerConnection.hpp"
#include "Avalon/IO/ConnectException.hpp"
#include "Avalon/IO/NotConnectedException.hpp"
#include "Avalon/IO/Reader.hpp"
#include "Avalon/IO/Writer.hpp"
#include "Avalon/WebServices/HttpClientChannel.hpp"
#include "Avalon/WebServices/HttpServerRequest.hpp"
#include "Avalon/WebServices/HttpServerResponse.hpp"
#include "Avalon/WebServices/InvalidHttpRequestException.hpp"

using namespace Avalon;
using namespace Avalon::IO;
using namespace Avalon::SignalHandling;
using namespace Avalon::Threading;
using namespace Avalon::WebServices;
using namespace boost;
using namespace boost::posix_time;
using namespace boost::signals2;
using namespace std;

HttpServer::RequestSignalEntry::RequestSignalEntry(
    const function<bool (const HttpServerRequest*)>& predicate)
    : m_predicate(predicate) {}

const time_duration HttpServer::KEEP_ALIVE = seconds(5);

HttpServer::HttpServer(ServerConnection* connection,
    Timer* keepAliveTimer, time_duration keepAlive, ThreadPool& threadPool)
    : m_connection(new ChannelAdapterServerConnection(connection,
        new WrapperChannelFactory(WrapperChannelFactory::ChannelCreator(
        &WrapperChannelFactory::WrapChannel<HttpClientChannel>)))),
      m_isConnected(false),
      m_isShuttingDown(false),
      m_keepAliveTimer(keepAliveTimer),
      m_keepAlive(keepAlive),
      m_acceptSlot(bind(&HttpServer::OnConnectionAccepted, this)),
      m_signalHandler(threadPool) {}

HttpServer::~HttpServer() {
  Close();
  Clear(m_requestSignals);
}

void HttpServer::Start() {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  if(m_isConnected) {
    return;
  }
  m_connection->Start();
  bool isOpen;
  connection closedConnection = m_connection->ConnectClosedSignal(
    m_signalHandler.GetSlot<Connection::ClosedSignal>(bind(
    &HttpServer::OnConnectionClosed, this)), Store(isOpen));
  m_connectionState.Initialize(isOpen, closedConnection);
  if(!m_connectionState.IsOpen()) {
    Shutdown();
    if(m_isConnected) {
      m_closedCondition.wait(lock);
    }
    BOOST_THROW_EXCEPTION(ConnectException());
  }
  m_isConnected = true;
  m_acceptResult.ConnectFinishedSignal(
    m_signalHandler.GetSlot<Async<Channel*>::FinishedSignal>(m_acceptSlot,
    Store(m_acceptState));
  m_connection->Accept(Store(m_acceptResult));
}

connection HttpServer::ConnectRequestSignal(
    const HttpRequestPredicate& predicate,
    const HttpRequestSignal::slot_type& slot) const {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  RequestSignalEntry* entry = new RequestSignalEntry(predicate);
  m_requestSignals.push_back(entry);
  return entry->m_signal.connect(slot);
}

bool HttpServer::IsConnected() const {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  return m_isConnected;
}

void HttpServer::Close() {
  boost::unique_lock<boost::mutex> lock(m_mutex);
  if(!m_isRunning) {
    return;
  }
  Shutdown();
  if(m_isRunning) {
    m_closedCondition.wait(lock);
  }
}

connection HttpServer::ConnectClosedSignal(
    const ClosedSignal::slot_type& slot) const {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  return m_closedSignal.connect(slot);
}

connection HttpServer::ConnectClosedSignal(
    const ClosedSignal::slot_type& slot, Out<bool> isConnected) const {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  *isConnected = m_isConnected;
  return m_closedSignal.connect(slot);
}

void HttpServer::SendResponse(HttpServerResponse* response,
    HttpClientChannel* channel) {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  if(!m_isConnected) {
    delete response;
    return;
  }
  channel->GetWriter().Write(response->Serialize());
  delete response;
}

void HttpServer::Shutdown() {
  m_isShuttingDown = true;
  if(m_connectionState.IsOpen()) {
    m_connection->Close();
    return;
  } else if(m_acceptState == Async<Channel*>::PENDING) {
    return;
  } else if(!m_clients.empty()) {
    for(vector<HttpClientChannel*>::const_iterator i = m_clients.begin();
        i != m_clients.end(); ++i) {
      (*i)->GetConnection().Close();
    }
    return;
  }
  m_isShuttingDown = false;
  m_isConnected = false;
  m_closedCondition.notify_one();
  m_closedSignal();
}

void HttpServer::RemoveChannel(HttpClientChannel* channel) {
  if(!channel->IsReadPending()) {
    Remove(m_clients, channel);
    delete channel;
    if(m_isShuttingDown && m_clients.empty()) {
      Shutdown();
    }
  }
}

void HttpServer::OnConnectionAccepted(const Async<Channel*>::Ptr& result) {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  m_acceptState = m_acceptResult.GetState();
  if(m_acceptState == Async<Channel*>::EXCEPTION) {
    m_acceptResult.Reset();
    if(m_isShuttingDown) {
      Shutdown();
    }
    return;
  }
  HttpClientChannel* channel = static_cast<HttpClientChannel*>(
    m_acceptResult.Get());
  m_acceptResult.Reset();
  m_acceptResult.ConnectFinishedSignal(
    m_signalHandler.GetSlot<Async<Channel*>::FinishedSignal>(m_acceptSlot,
    Store(m_acceptState));
  m_connection->Accept(Store(m_acceptResult));
  bool isOpen;
  connection closedConnection = channel->GetConnection().ConnectClosedSignal(
    m_signalHandler.GetSlot<Connection::ClosedSignal>(bind(
    &HttpServer::OnClientClosed, this, channel)), Store(isOpen));
  channel->GetChannelState().Initialize(isOpen, closedConnection);
  if(!channel->GetChannelState().IsOpen()) {
    delete channel;
    return;
  }
  m_clients.push_back(channel);
  channel->GetReadResult().ConnectFinishedSignal(
    m_signalHandler.GetSlot<BaseAsync::FinishedSignal>(channel->GetReadSlot()),
    Store(channel->GetReadState()));
  channel->GetReader().Read(&channel->GetReadBuffer(),
    Store(channel->GetReadResult()));
}

void HttpServer::OnConnectionClosed() {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  m_connectionState.SetClosed();
  Shutdown();
}

void HttpServer::OnClientClosed(HttpClientChannel* channel) {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  channel->GetChannelState().SetClosed();
  m_channelClosedConnections.Disconnect(channel);
  RemoveChannel(channel);
}

void HttpServer::OnClientRead(HttpClientChannel* channel) {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  channel->GetReadState() = channel->GetReadResult().GetState();
  if(channel->GetReadState() == Async<int>::EXCEPTION) {

    // TODO
    channel->GetConnection().Close();
    return;
  }
  if(!channel->GetChannelState().IsOpen()) {
    RemoveChannel(channel);
    return;
  }
  if(result->GetState() == BaseAsync::EXCEPTION) {
    channel->GetConnection().Close();
    return;
  }
  HttpRequestParser& parser = channel->GetHttpRequestParser();
  Buffer local;
  local.Append(channel->GetReadBuffer().GetData(), result->Get());
  parser.Feed(local);
  channel->GetReadBuffer().Reset();
  channel->SetReadPending(true);
  Async<int>::Ptr nextReadResult = channel->GetReader().Read(
    &channel->GetReadBuffer());
  nextReadResult->ConnectFinishedSignal(m_signalHandler.GetSlot<
    BaseAsync::FinishedSignal>(bind(&HttpServer::OnClientRead, this,
    nextReadResult, channel)));
  while(true) {
    HttpServerRequest* request;
    try {
      request = parser.Parse();
    } catch(InvalidHttpRequestException&) {
      request = new HttpServerRequest(make_tuple(1, 1), HttpMethod::NONE,
        Uri(), map<string, string>(), vector<Cookie>(), Buffer());
      HttpServerResponse* response = new HttpServerResponse(this, channel,
        request);
      response->SetStatus(HttpStatusCode::BAD_REQUEST);
      response->SendResponse();
      channel->GetConnection().Close();
      return;
    }
    if(request == NULL) {
      break;
    }
    HttpServerResponse* response = new HttpServerResponse(this, channel,
      request);
    bool foundSignal = false;
    for(vector<RequestSignalEntry*>::const_iterator i =
        m_requestSignals.begin(); i != m_requestSignals.end(); ++i) {
      if((*i)->m_predicate(request)) {
        (*i)->m_signal(request, response);
        foundSignal = true;
        break;
      }
    }
    if(!foundSignal) {
      response->SetStatus(HttpStatusCode::NOT_FOUND);
      response->SendResponse();
    }
  }
}
